package com.knife.mybatisplus.extension.plugins.inner;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.baomidou.mybatisplus.core.conditions.update.Update;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.mapper.Mapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.knife.mybatisplus.extension.plugins.handler.UserLineHandler;
import com.knife.oauth.domain.entity.KnifeUserDetails;
import com.knife.oauth.util.DetailHelper;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author tianhao.luo@hand-china.com
 * @version 1.0
 * @date 2022年02月27日 星期日 11:25 上午
 * @description
 * @since 1.0
 */
@SuppressWarnings({"unchecked"})
public class UserLineInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
    /**
     * entity类缓存
     */
    private static final Map<String, Class<?>> ENTITY_CLASS_CACHE = new ConcurrentHashMap<>();
    /**
     * 变量占位符正则
     */
    private static final Pattern PARAM_PAIRS_RE = Pattern.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
    /**
     * paramNameValuePairs存放的updatedBy值的key
     */
    private static final String UPDATED_BY_VAL_KEY = "#updatedByVal#";
    /**
     * paramNameValuePairs存放的updatedDate值的key
     */
    private static final String UPDATED_DATE_VAL_KEY = "#updatedDateVal#";
    /**
     * Support wrapper mode
     */
    private final boolean wrapperMode;
    /**
     * 自定义拦截器
     *
     * @description
     * @version 1.0
     * @author tianhao.luo@hand-china.com
     * @date 2022/2/27 10:49 下午
     * @since 1.0
     */
    private final UserLineHandler userLineHandler;
    private RuntimeException exception;

    public UserLineInnerInterceptor(UserLineHandler userLineHandler) {
        this(false, userLineHandler);
    }

    public UserLineInnerInterceptor(boolean wrapperMode, UserLineHandler userLineHandler) {
        this.wrapperMode = wrapperMode;
        this.userLineHandler = userLineHandler;
    }

    public void setException(RuntimeException exception) {
        this.exception = exception;
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        // 针对插入和更新操作做解析处理
        if (SqlCommandType.INSERT == ms.getSqlCommandType()) {
            // mp的实体插入操作
            doInsert(parameter, ms.getId());
        } else if (SqlCommandType.UPDATE == ms.getSqlCommandType()) {
            // mp的更新操作
            if (parameter instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) parameter;
                doUpdate(map, ms.getId());
            }
        }
    }

    /**
     * 是否忽略对实体对应的表进行操作
     * @description
     * @since 1.0
     * @version 1.0
     * @author tianhao.luo@hand-china.com
     * @date 2022/2/27 11:18 下午
     * @param entityClass 实体类
     * @return 是否忽略对该表进行操作
     */
    private boolean ignoreUserLineTable(Class<?> entityClass) {
        TableName table = entityClass.getAnnotation(TableName.class);
        String tableName;
        if (table != null) {
            tableName = table.value();
        } else {
            // 通过类名转化
            String classSimpleName = entityClass.getSimpleName();
            tableName = StringUtils.camelToUnderline(classSimpleName);
        }
        return userLineHandler.ignoreTable(tableName);
    }


    /**
     * 处理mp的通用插入，记录插入操作的用户和时间；处理的操作为{@link BaseMapper#insert(Object)}
     *
     * @param parameter mp插入的实体对象
     * @param msId      mapper的sqlId
     * @description
     * @version 1.0
     * @author tianhao.luo@hand-china.com
     * @date 2022/2/27 10:44 下午
     * @since 1.0
     */
    protected void doInsert(Object parameter, String msId) {
        try {
            Class<?> entityClass = computeIfAbsent(msId);
            if (ignoreUserLineTable(entityClass)) {
                return;
            }
            String createdDateColumn = userLineHandler.getCreatedDateColumn(),
                    createdByIdColumn = userLineHandler.getCreatedByIdColumn(),
                    updatedByIdColumn = userLineHandler.getUpdatedByIdColumn(),
                    updatedDateColumn = userLineHandler.getUpdatedDateColumn()
            ;
            if (StringUtils.isEmpty(createdDateColumn)
                    || StringUtils.isEmpty(createdByIdColumn)
                    || StringUtils.isEmpty(updatedByIdColumn)
                    || StringUtils.isEmpty(updatedDateColumn)) {
                return;
            }
            final Field createdDateField = getFieldByColumn(createdDateColumn, entityClass);
            final Field createdByIdField = getFieldByColumn(createdByIdColumn, entityClass);
            final Field updatedByIdField = getFieldByColumn(updatedByIdColumn, entityClass);
            final Field updatedDateField = getFieldByColumn(updatedDateColumn, entityClass);

            if (createdDateField == null
                    || createdByIdField == null
                    || updatedByIdField == null
                    || updatedDateField == null) {
                return;
            }

            Object createdDate = createdDateField.get(parameter);
            Object createdById = createdByIdField.get(parameter);
            Object updatedById = updatedByIdField.get(parameter);
            Object updateDate = updatedDateField.get(parameter);
            // 对于非空的字段是否要处理？此处认为如果有给值就不做处理了
            if (createdDate == null && createdDateField.getType().equals(Date.class)) {
                createdDate = new Date();
            }
            if (updateDate == null && updatedDateField.getType().equals(Date.class)) {
                updateDate = new Date();
            }
            if (createdById == null && (createdByIdField.getType().equals(Integer.class) || createdByIdField.getType().equals(Long.class))) {
                createdById = Optional.ofNullable(DetailHelper.getUserDetails()).map(KnifeUserDetails::getUserId).orElse(-1L);
            }
            if (updatedById == null && (updatedByIdField.getType().equals(Integer.class) || updatedByIdField.getType().equals(Long.class))) {
                updatedById = Optional.ofNullable(DetailHelper.getUserDetails()).map(KnifeUserDetails::getUserId).orElse(-1L);
            }
            createdDateField.set(parameter, createdDate);
            createdByIdField.set(parameter, createdById);
            updatedDateField.set(parameter, updateDate);
            updatedByIdField.set(parameter, updatedById);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * @description
     * @since 1.0
     * @version 1.0
     * @author tianhao.luo@hand-china.com
     * @date 2022/2/27 11:30 下午
     * @param column 数据库列名
     * @param cls 实体类
     * @return 反射得到的属性
     */
    private Field getFieldByColumn(String column, Class<?> cls) {
        if (!StringUtils.isCamel(column)) {
            column = StringUtils.underlineToCamel(column);
        }
        return FieldUtils.getField(cls, column, true);
    }


    private void setUpdatedIdAndDateByWrapper(Map<String, Object> map, String msId) {
        Object ew = map.get(Constants.WRAPPER);
        if (null != ew && ew instanceof AbstractWrapper && ew instanceof Update) {
            Class<?> entityClass = computeIfAbsent(msId);
            if (ignoreUserLineTable(entityClass)) {
                return;
            }

            String updatedDateColumn = userLineHandler.getUpdatedDateColumn(), updatedByIdColumn = userLineHandler.getUpdatedByIdColumn();
            if (StringUtils.isEmpty(updatedByIdColumn) || StringUtils.isEmpty(updatedDateColumn)) {
                return;
            }
            final Field updatedDateField = getFieldByColumn(updatedDateColumn, entityClass);
            final Field updatedByIdField = getFieldByColumn(updatedByIdColumn, entityClass);

            if (updatedByIdField == null || updatedDateField == null) {
                return;
            }

            final UserLineInnerInterceptor.FieldEqFinder updatedDateFieldEqFinder = new UserLineInnerInterceptor.FieldEqFinder(updatedDateColumn, (Wrapper<?>) ew);
            final UserLineInnerInterceptor.FieldEqFinder updatedByIdFieldEqFinder = new UserLineInnerInterceptor.FieldEqFinder(updatedByIdColumn, (Wrapper<?>) ew);
            if (!updatedDateFieldEqFinder.isPresent() && !updatedByIdFieldEqFinder.isPresent()) {
                return;
            }
            final Map<String, Object> paramNameValuePairs = ((AbstractWrapper<?, ?, ?>) ew).getParamNameValuePairs();
            Object updatedDate = new Date();
            Object updatedById = Optional.ofNullable(DetailHelper.getUserDetails()).map(KnifeUserDetails::getUserId).orElse(-1L);
            ;
            // 拼接新的version值
            paramNameValuePairs.put(UPDATED_BY_VAL_KEY, updatedById);
            paramNameValuePairs.put(UPDATED_DATE_VAL_KEY, updatedDate);
            ((Update<?, ?>) ew).setSql(String.format("%s = #{%s.%s}", updatedByIdColumn, "ew.paramNameValuePairs", UPDATED_BY_VAL_KEY));
            ((Update<?, ?>) ew).setSql(String.format("%s = #{%s.%s}", updatedDateColumn, "ew.paramNameValuePairs", UPDATED_DATE_VAL_KEY));
        }
    }


    private Class<?> computeIfAbsent(String msId) {
        Class<?> entityClass = ENTITY_CLASS_CACHE.get(msId);
        if (null == entityClass) {
            try {
                final String className = msId.substring(0, msId.lastIndexOf('.'));
                entityClass = ReflectionKit.getSuperClassGenericType(Class.forName(className), Mapper.class, 0);
                ENTITY_CLASS_CACHE.put(msId, entityClass);
            } catch (ClassNotFoundException e) {
                throw ExceptionUtils.mpe(e);
            }
        }
        return entityClass;
    }


    /**
     * 处理mp的通用更新，记录更新操作的用户和时间；参考版本锁拦截器和租户拦截器生成
     * 处理的操作为{@link BaseMapper#updateById(Object)}和{@link BaseMapper#update(Object, Wrapper)}
     *
     * @param map  mp方法参数
     * @param msId mapper方法sqlId
     * @description
     * @version 1.0
     * @author tianhao.luo@hand-china.com
     * @date 2022/2/27 10:47 下午
     * @since 1.0
     */
    protected void doUpdate(Map<String, Object> map, String msId) {
        // updateById(et), update(et, wrapper);
        Object et = map.getOrDefault(Constants.ENTITY, null);
        if (Objects.nonNull(et)) {
            try {
                Class<?> entityClass = computeIfAbsent(msId);
                if (ignoreUserLineTable(entityClass)) {
                    return;
                }

                String updatedDateColumn = userLineHandler.getUpdatedDateColumn(), updatedByIdColumn = userLineHandler.getUpdatedByIdColumn();
                if (StringUtils.isEmpty(updatedByIdColumn) || StringUtils.isEmpty(updatedDateColumn)) {
                    return;
                }
                Field updatedDateField = getFieldByColumn(updatedDateColumn, et.getClass());
                Field updatedByIdField = getFieldByColumn(updatedByIdColumn, et.getClass());
                Object updatedDate = new Date();
                Object updatedById = Optional.ofNullable(DetailHelper.getUserDetails()).map(KnifeUserDetails::getUserId).orElse(-1L);
                updatedDateField.set(et, updatedDate);
                updatedByIdField.set(et, updatedById);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }// update(LambdaUpdateWrapper) or update(UpdateWrapper)
        else if (wrapperMode && map.entrySet().stream().anyMatch(t -> Objects.equals(t.getKey(), Constants.WRAPPER))) {
            setUpdatedIdAndDateByWrapper(map, msId);
        }
    }


    /**
     * EQ字段查找器
     */
    private static class FieldEqFinder {

        /**
         * 字段名
         */
        private final String fieldName;
        /**
         * 字段值的key
         */
        private String valueKey;
        /**
         * 当前状态
         */
        private UserLineInnerInterceptor.FieldEqFinder.State state;

        public FieldEqFinder(String fieldName, Wrapper<?> wrapper) {
            this.fieldName = fieldName;
            state = UserLineInnerInterceptor.FieldEqFinder.State.INIT;
            find(wrapper);
        }

        /**
         * 是否已存在
         */
        public boolean isPresent() {
            return state == UserLineInnerInterceptor.FieldEqFinder.State.VERSION_VALUE_PRESENT;
        }

        private boolean find(Wrapper<?> wrapper) {
            Matcher matcher;
            final NormalSegmentList segments = wrapper.getExpression().getNormal();
            for (ISqlSegment segment : segments) {
                // 如果字段已找到并且当前segment为EQ
                if (state == UserLineInnerInterceptor.FieldEqFinder.State.FIELD_FOUND && segment == SqlKeyword.EQ) {
                    this.state = UserLineInnerInterceptor.FieldEqFinder.State.EQ_FOUND;
                    // 如果EQ找到并且value已找到
                } else if (state == UserLineInnerInterceptor.FieldEqFinder.State.EQ_FOUND
                        && (matcher = PARAM_PAIRS_RE.matcher(segment.getSqlSegment())).matches()) {
                    this.valueKey = matcher.group(1);
                    this.state = UserLineInnerInterceptor.FieldEqFinder.State.VERSION_VALUE_PRESENT;
                    return true;
                    // 处理嵌套
                } else if (segment instanceof Wrapper) {
                    if (find((Wrapper<?>) segment)) {
                        return true;
                    }
                    // 判断字段是否是要查找字段
                } else if (segment.getSqlSegment().equals(this.fieldName)) {
                    this.state = UserLineInnerInterceptor.FieldEqFinder.State.FIELD_FOUND;
                }
            }
            return false;
        }

        /**
         * 状态机
         */
        enum State {
            INIT,
            FIELD_FOUND,
            EQ_FOUND,
            VERSION_VALUE_PRESENT;

        }
    }
}
